package net.daum.mail;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.mail.BodyPart;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Part;
import javax.mail.Message.RecipientType;
import javax.mail.internet.ContentType;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;
import javax.mail.internet.ParseException;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;

import com.sun.mail.imap.IMAPInputStream;
import com.sun.mail.imap.IMAPNestedMessage;
import com.sun.mail.util.BASE64DecoderStream;
import com.sun.mail.util.PropUtil;

import net.daum.mail.exception.MimeRenderException;
import net.daum.mail.util.MimeStringUtils;
import net.daum.mail.util.MimeUtils;

public class MimeRendererImpl implements MimeRenderer {

	protected String text = new String();
	protected Message message;
	protected List<Part> attachments = new ArrayList<Part>();
	
	protected String baseDir = "maildata";
	protected String filePrefix = "";
	protected boolean attachmentOnly;
	
	protected boolean ignoreUnknownEncoding = PropUtil.getBooleanSystemProperty("mail.mime.ignoreunknownencoding", false);
	
	public MimeRendererImpl(Message message) {
		this.message = message;
	}

	public MimeRendererImpl(Message message, String filePrefix) {
		this.message = message;
		this.filePrefix = filePrefix;
	}

	public MimeRendererImpl(Message message, String filePrefix, String baseDir) {
		this.message = message;
		this.filePrefix = filePrefix;
		this.baseDir = baseDir;
	}

	@Override
	public void render() throws MimeRenderException {
		renderMessage(message);
	}
	
	@Override
	public void renderForAttachment() throws MimeRenderException {
		attachmentOnly = true;
		renderMessage(message);
		attachmentOnly = false;
	}
	
	protected void renderMessage(Part message) throws MimeRenderException {
		try {
			if (StringUtils.equalsIgnoreCase(Part.ATTACHMENT, message.getDisposition())) {
				renderAttachment(message, Part.ATTACHMENT);
			} else {
				if (!canRenderContent(message.getContentType())) {
					return;
				}
				Object content = message.getContent();
				
				if (content instanceof MimeMultipart) {
					renderMultipart((MimeMultipart) content);
				} else if (content instanceof IMAPNestedMessage) {
					renderNestedMessage((MimeMessage) content);
				} else if (content instanceof MimeMessage) {
					renderNestedMessage((MimeMessage) content);
				} else if (content instanceof String) {
					renderString((String) content, message);
				} else if (content instanceof BASE64DecoderStream) {
					renderInlineAttachment(message);
				} else if (content instanceof IMAPInputStream) {
					renderInputStream((IMAPInputStream) content);
				} else {
					renderAttachment(message, Part.ATTACHMENT);
				}
			}
			
			
		} catch(Exception e) {
			if (e instanceof UnsupportedEncodingException) {
				if (!ignoreUnknownEncoding) {
					throw new MimeRenderException(e);
				}
			} else {
				throw new MimeRenderException(e);
			}
		}
	}

	protected boolean canRenderContent(String messageContentType) {
		boolean result = true;
		try {
			ContentType contentType = new ContentType(messageContentType);
			if (StringUtils.equalsIgnoreCase("text", contentType.getPrimaryType()) && attachmentOnly) {
				result = false;
			}
		} catch (ParseException e) {
			// just return true
		}
		return result;
	}
	
	
	protected void renderInputStream(IMAPInputStream input) throws IOException {
		if (!attachmentOnly) {
			String content = MimeStringUtils.getStringFromInputStream(input);
			text += MimeStringUtils.plainToHtml(content);
		}
	}

	protected void renderInlineAttachment(Part message) throws MessagingException {
		if (!attachmentOnly) {
			String fileName = String.valueOf(attachments.size()); 
			String relatedFilename = makeRelatedFilename(fileName);
			if (StringUtils.startsWithIgnoreCase(message.getContentType(), "image")) {
				text += "<img src=\"" + relatedFilename + "\">";
			}
		}
		renderAttachment(message, Part.INLINE);
	}
	
	protected String makeRelatedFilename(String filename) {
		String decodeFilename;
		try {
			if (StringUtils.isEmpty(filename)) {
				decodeFilename = "NONAME";
			} else {
				decodeFilename = MimeUtility.decodeText(filename);
			}
		} catch (Exception e) {
			decodeFilename = filename;
		}
		return baseDir + File.separatorChar + filePrefix + decodeFilename;
	}
	
	protected void renderAttachment(Part message, String defaultDisposition) throws MessagingException {
		// don't use defaultDisposition on this version
		// but can use on overrided renderAttachment
		
		int nextIndex = attachments.size();
		attachments.add(nextIndex, message);
	}

	protected void renderString(String content, Part message) throws MessagingException {
		if (!attachmentOnly) {
			String subType = MimeUtils.getSubType(message.getContentType());
			if (StringUtils.containsIgnoreCase(subType, "plain")) {
				text += MimeStringUtils.plainToHtml(content);
			} else {
				text += content;
			}
		}
	}

	protected void renderMultipart(MimeMultipart content) throws Exception {
		ContentType contentType = new ContentType(content.getContentType());
		String lowerSubtype = StringUtils.lowerCase(contentType.getSubType());
		if (StringUtils.equalsIgnoreCase("alternative", lowerSubtype)) {
			renderMultipartAlternative(content);
		} else if (StringUtils.equalsIgnoreCase("related", lowerSubtype)) {
			renderMultipartRelated(content);
		} else if (StringUtils.equalsIgnoreCase("report", lowerSubtype)) {
			renderMultipartReport(content);
		} else {
			renderMultipartMixed(content);
		}
	}
	
	protected void renderMultipartMixed(MimeMultipart content) throws Exception {
		try {
			for(int size = content.getCount(), i = 0 ; i < size ; i++) {
				BodyPart part = content.getBodyPart(i);
				renderMessage(part);
			}
		} catch (Exception e) {
			throw e;
		}
	}

	protected void renderMultipartRelated(MimeMultipart content) throws Exception {
		try {
			if (content.getCount() == 0) {
				return;
			}
			
			renderMessage(content.getBodyPart(0));
			for(int size = content.getCount(), i = 1 ; i < size ; i++) {
				BodyPart part = content.getBodyPart(i);
				renderAttachment(part, Part.INLINE);
			}
		} catch (Exception e) {
			throw e;
		}
	}
	
	protected void renderMultipartReport(MimeMultipart content) throws Exception {
		if (!MimeUtils.isMultipartReport(content)) {
			renderMultipartMixed(content);
			return;
		}
		
		text += "<div class='dm__report_wrap'>" +
				"<h2 class='dm__report_title'>메일 전송이 정상적으로 이루어지지 못하였습니다.</h2>\n";
		try {
			BodyPart part = content.getBodyPart(1);
			String statusBody = MimeStringUtils.getStringFromInputStream((IMAPInputStream) part.getContent());
			
			Map<String, String> statusHeaders = MimeUtils.makeHeadersMapFromBody(statusBody);
			String finalRecipient = statusHeaders.get("Final-Recipient");
			String status = statusHeaders.get("Status");
			String orgSendDate = statusHeaders.get("Arrival-Date");
			
			if (orgSendDate == null || finalRecipient == null || status == null) {
				throw new Exception("No status data");
			}
			
			text += "<p class='dm__report_msg'>회원님께서 " + orgSendDate  + " 에 <address>" + finalRecipient + "</address> 으로 발송하신 메일이 되돌아 왔습니다.</p>\n" +
					"<dl><dt class='dm__report_sub_title'>반송된 이유</dt>\n" +
					"<dd class='dm__report_sub_msg'>" + MimeUtils.getMessageReportReason(status) + "</dd>\n" +
					"<dt class='dm__report_sub_title'>이렇게 해보세요.</dt>\n" +
					"<dd class='dm__report_sub_msg'>" + MimeUtils.getMessageReportHelpMessage(status) + "</dd></dl>\n";
		} catch (Exception e) {
			text += "<p class='dm__report_msg'>회원님께서 발송하신 메일이 되돌아 왔습니다.</p>\n";
		}
		
		text += "<div class='dm__report_org'>";
		renderMessage(content.getBodyPart(0));
		renderMessage(content.getBodyPart(1));
		text += "</div></div>";
		
		renderMessage(content.getBodyPart(2));
	}

	protected void renderMultipartAlternative(MimeMultipart content) throws Exception {
		try {
			BodyPart targetPart = null;
			for(int size = content.getCount(), i = 0 ; i < size ; i++) {
				BodyPart part = content.getBodyPart(i);
				ContentType contentType = new ContentType(part.getContentType());
				if (canRenderText(contentType)) {
					targetPart = part;
				}
				
				if (targetPart != null && StringUtils.equalsIgnoreCase("html", contentType.getSubType())) {
					break;
				}
			}
			if (targetPart != null) {
				renderMessage(targetPart);
			}
		} catch (Exception e) {
			throw e;
		}
	}
	
	protected void renderNestedMessage(MimeMessage message) throws Exception {
		if (!attachmentOnly) {
			text += makeNestedMessageHeader(message);
		}
		renderMessage(message);
		if (!attachmentOnly) {
			text += makeNestedMessageFooter(message);
		}
		
		renderAttachment(message, Part.INLINE);
	}

	protected String makeNestedMessageHeader(MimeMessage message) throws MessagingException {
		StringBuffer buf = new StringBuffer();
		buf.append("<div class='dm__nested'>");
		buf.append("<div class='dm__nested_header'>");
		buf.append("--------- Original Message ---------<br>\n");
		if (ArrayUtils.getLength(message.getFrom()) > 0) {
			buf.append("<b>From</b>: "); buf.append(MimeStringUtils.addressesToUnicodeString((InternetAddress[]) message.getFrom())); buf.append("<br>\n");
		}
		if (ArrayUtils.getLength(message.getRecipients(RecipientType.TO)) > 0) {
			buf.append("<b>To</b>: "); buf.append(MimeStringUtils.addressesToUnicodeString((InternetAddress[]) message.getRecipients(RecipientType.TO))); buf.append("<br>\n");
		}
		if (ArrayUtils.getLength(message.getRecipients(RecipientType.CC)) > 0) {
			buf.append("<b>CC</b>: "); buf.append(MimeStringUtils.addressesToUnicodeString((InternetAddress[]) message.getRecipients(RecipientType.CC))); buf.append("<br>\n");
		}
		if (ArrayUtils.getLength(message.getRecipients(RecipientType.BCC)) > 0) {
			buf.append("<b>BCC</b>: "); buf.append(MimeStringUtils.addressesToUnicodeString((InternetAddress[]) message.getRecipients(RecipientType.BCC))); buf.append("<br>\n");
		}
		buf.append("<b>Date</b>: "); buf.append(message.getSentDate().toString()); buf.append("<br>\n");
		buf.append("<b>Subject</b>: "); buf.append(message.getSubject()); buf.append("<br>\n");
		buf.append("</div>\n");
		return buf.toString();
	}
	
	protected String makeNestedMessageFooter(MimeMessage message) {
		return "</div>";
	}
	
	private boolean canRenderText(ContentType contentType) {
		boolean result = false;
		
		if (contentType != null
				&& (StringUtils.equalsIgnoreCase("html", contentType.getSubType())
					|| StringUtils.equalsIgnoreCase("plain", contentType.getSubType())
					|| StringUtils.equalsIgnoreCase("multipart", contentType.getPrimaryType()))) {
			result = true;
		}
		
		return result;
	}

	@Override
	public void saveText() throws IOException {
		saveFile("message", new ByteArrayInputStream(text.getBytes()));
	}
	
	@Override
	public void saveAttachment(int index) throws Exception {
		Part attachment = this.attachments.get(index);
		InputStream is = attachment.getInputStream();
		String fileName = "" + index;
		saveFile(fileName, is);
	}
	
	private void saveFile(String filename, InputStream in) throws IOException {
		try {
			makeBaseDir();
			File file = new File(makeRelatedFilename(filename));
			file.createNewFile();
			FileOutputStream out = new FileOutputStream(file);
			byte[] buf = new byte[1024];
			int readCount = 0;
			while((readCount = in.read(buf)) > -1) {
				out.write(buf, 0, readCount);
			}
			out.close();
		} catch(IOException e) {
			throw e;
		}
	}

	private void makeBaseDir() throws IOException {
		File dir = new File(baseDir);
		if (dir.exists()) {
			return;
		}
		
		boolean result = dir.mkdirs();
		if (!result) {
			throw new IOException();
		}
	}
	
	@Override
	public String getText() {
		return text;
	}

	@Override
	public Message getMessage() {
		return message;
	}

	@Override
	public List<Part> getAttachments() {
		return attachments;
	}


}
